#pragma once

#include "vector3.h"
#include "matrix4.h"
#include "AxisAlignedBox.h"

class ExUtilityFunction
{
public:

	static float snapValue( float v, float snapSize )
	{
		if ( snapSize > 0.f )
		{
			float halfSnap = snapSize / 2.f;

			if (v > 0.f)
			{
				v += halfSnap;

				v -= ( fmodf( v, snapSize ) );
			}
			else
			{
				v -= halfSnap;

				v += ( fmodf( -v, snapSize ) ); 
			}
		}

		return v;
	}

	static Common::Vector3 SnapPoint3( const Point3& pt, float snapSize = 0.0001f )
	{
		Point3 p = pt;

		p.x = snapValue( p.x, snapSize );
		p.y = snapValue( p.y, snapSize );
		p.z = snapValue( p.z, snapSize );

		return Common::Vector3( p.x, p.y, p.z );
	}

	static bool IsMirrored( const Matrix3& mat )
	{
		return DotProd( CrossProd( mat.GetRow(0), mat.GetRow(1) ), mat.GetRow(2) ) < 0.0f;
	}

	static Point3 ApplyUnitScale( const Point3& p )
	{
		return p * (float)GetMasterScale( UNITS_CENTIMETERS ) / 100;
	}

	static Matrix3 ApplyUnitScale( const Matrix3& m )
	{
		Matrix3 m2 = m;
		m2.SetRow( 3, m.GetRow( 3 ) * (float)GetMasterScale( UNITS_CENTIMETERS ) / 100 );
		return m2;
	}

	static Common::Plane ApplyUnitScale( const Common::Plane& plane )
	{
		return Common::Plane( plane.normal, (float)GetMasterScale( UNITS_CENTIMETERS ) / 100 * plane.d );
	}

	static Common::Vector3 ApplyUnitScale( const Common::Vector3& p )
	{
		return p * (float)GetMasterScale( UNITS_CENTIMETERS ) / 100;
	}

	static Common::AxisAlignedBox ApplyUnitScale( const Common::AxisAlignedBox& aabb )
	{
		float fScale = (float)GetMasterScale( UNITS_CENTIMETERS ) / 100;
		return Common::AxisAlignedBox( aabb.getMinimum() * fScale, aabb.getMaximum() * fScale );
	}

	static void Convert( const Point3& p1, Common::Vector3& p2 )
	{
		p2.x = p1.x;
		p2.y = p1.y;
		p2.z = p1.z;
	}

	static void Convert( const Common::Vector3& p1, Point3& p2 )
	{
		p2.x = p1.x;
		p2.y = p1.y;
		p2.z = p1.z;
	}

	static void Convert( const Common::Matrix4& lhsMat, Matrix3& rhsMat )
	{
		rhsMat.IdentityMatrix();
		for( int iCol = 0; iCol != 4; ++iCol )
		{
			const Real* pRowData = lhsMat[iCol];
			rhsMat.SetRow( iCol, Point3(pRowData[0],pRowData[1],pRowData[2]) );
		}
	}

	static void Convert( const Matrix3& lhsMat, Common::Matrix4& rhsMat )
	{
		rhsMat = Common::Matrix4::IDENTITY;
		for( int iRow = 0; iRow != 4; ++iRow )
		{
			Point3 rRow = lhsMat.GetRow(iRow);
			Real* pRow = rhsMat[iRow];
			for( int iCol = 0; iCol != 3; ++iCol )
				pRow[iCol] = rRow[iCol];
		}
	}

	static Point3 MaxToDx( const Point3& vMaxVector )
	{
		return Point3( vMaxVector.x , vMaxVector.z , vMaxVector.y  );
	}

	static Matrix3 MaxToDx( const Matrix3& matMaxMatrix )
	{
		static Matrix3 s_matMaxToDx;
		s_matMaxToDx.IdentityMatrix();
		Point3 row1 = s_matMaxToDx.GetRow( 1 );
		s_matMaxToDx.SetRow( 1, s_matMaxToDx.GetRow( 2 ) );
		s_matMaxToDx.SetRow( 2, row1 );

		return matMaxMatrix * s_matMaxToDx;
	}

	static Common::Vector3 MaxToDx( const Common::Vector3& vMaxVector )
	{
		return Common::Vector3( vMaxVector.x , vMaxVector.z, vMaxVector.y );
	}

	static Common::Vector2 MaxToDx( const Common::Vector2& vMaxVector )
	{
		return vMaxVector;
	}
	
	static Common::Matrix3 MaxToDx( const Common::Matrix3& matMaxMatrix )
	{
		static Common::Matrix3 s_matMaxToDx (
			1.0f, 0.0f, 0.0f,
			0.0f, 0.0f, 1.0f,
			0.0f, 1.0f, 0.0f
		);
		
		return s_matMaxToDx * matMaxMatrix * s_matMaxToDx;
	}

	static Common::Matrix4 MaxToDx( const Common::Matrix4& matMaxMatrix )
	{
		static Common::Matrix4 s_matMaxToDx (
			1.0f, 0.0f, 0.0f, 0.0f,
			0.0f, 0.0f, 1.0f, 0.0f,
			0.0f, 1.0f, 0.0f, 0.0f,
			0.0f, 0.0f, 0.0f, 1.0f
		);
		
		return s_matMaxToDx * matMaxMatrix * s_matMaxToDx;
	}

	static Common::Plane MaxToDx( const Common::Plane& plaMax )
	{
		return Common::Plane( MaxToDx( plaMax.normal ) , plaMax.d );
	}

	static Common::AxisAlignedBox MaxToDx( const Common::AxisAlignedBox& aabb )
	{
		return Common::AxisAlignedBox( MaxToDx( aabb.getMinimum() ), MaxToDx( aabb.getMaximum() ) );
	}

	static Common::Plane DxToMax( const Common::Plane& plaDx )
	{
		return Common::Plane( MaxToDx( plaDx.normal ) , plaDx.d );
	}
};

inline bool operator < ( Point2 p1, Point2 p2 )
{
	if( p1.x < p2.x )	return true;
	if( p1.x > p2.x )	return false;
	if( p1.y < p2.y )	return true;
	if( p1.y > p2.y )	return false;
	return false;
}

inline bool operator > ( Point2 p1, Point2 p2 )
{
	if( p1.x > p2.x )	return true;
	if( p1.x < p2.x )	return false;
	if( p1.y > p2.y )	return true;
	if( p1.y < p2.y )	return false;
	return false;
}

inline bool operator < ( Point3 p1, Point3 p2 )
{
	if( p1.x < p2.x )	return true;
	if( p1.x > p2.x )	return false;
	if( p1.y < p2.y )	return true;
	if( p1.y > p2.y )	return false;
	if( p1.z < p2.z )	return true;
	if( p1.z > p2.z )	return false;
	return false;
}

inline bool operator > ( Point3 p1, Point3 p2 )
{
	if( p1.x > p2.x )	return true;
	if( p1.x < p2.x )	return false;
	if( p1.y > p2.y )	return true;
	if( p1.y < p2.y )	return false;
	if( p1.z > p2.z )	return true;
	if( p1.z < p2.z )	return false;
	return false;
}